/***********************************************************************
*
* Slimgress: Ingress API for Android
* Copyright (C) 2013 Norman Link <norman.link@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/
package com.norman0406.slimgress;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.TileProvider;
import com.google.android.gms.maps.model.UrlTileProvider;
import com.google.common.geometry.S2LatLng;
import com.google.common.geometry.S2LatLngRect;
import com.norman0406.slimgress.API.Common.Team;
import com.norman0406.slimgress.API.Game.GameState;
import com.norman0406.slimgress.API.GameEntity.GameEntityBase;
import com.norman0406.slimgress.API.GameEntity.GameEntityControlField;
import com.norman0406.slimgress.API.GameEntity.GameEntityLink;
import com.norman0406.slimgress.API.GameEntity.GameEntityPortal;
public class ScannerView extends SupportMapFragment
{
private IngressApplication mApp = IngressApplication.getInstance();
private GameState mGame = mApp.getGame();
private GoogleMap mMap = null;
private Bitmap mXMParticleIcon = null;
private Bitmap mPortalIconResistance = null;
private Bitmap mPortalIconEnlightened = null;
private Bitmap mPortalIconNeutral = null;
private HashMap<String, Marker> mMarkers = null;
private HashMap<String, Polyline> mLines = null;
private HashMap<String, Polygon> mPolygons = null;
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
// retrieve map
mMap = getMap();
if (mMap == null)
throw new RuntimeException("map is invalid");
loadAssets();
mMarkers = new HashMap<String, Marker>();
mLines = new HashMap<String, Polyline>();
mPolygons = new HashMap<String, Polygon>();
// disable most ui elements
UiSettings ui = mMap.getUiSettings();
ui.setAllGesturesEnabled(false);
ui.setCompassEnabled(false);
ui.setScrollGesturesEnabled(true);
ui.setRotateGesturesEnabled(true);
ui.setZoomControlsEnabled(false);
ui.setMyLocationButtonEnabled(false);
mMap.setMyLocationEnabled(true);
mMap.setOnMyLocationChangeListener(new GoogleMap.OnMyLocationChangeListener() {
boolean firstLocation = true;
@Override
public void onMyLocationChange(Location myLocation)
{
// update camera position
CameraPosition pos = mMap.getCameraPosition();
CameraPosition newPos = new CameraPosition.Builder(pos)
.target(new LatLng(myLocation.getLatitude(), myLocation.getLongitude()))
.zoom(16)
.tilt(40)
.build();
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(newPos));
// update game position
mGame.updateLocation(new com.norman0406.slimgress.API.Common.Location(myLocation.getLatitude(), myLocation.getLongitude()));
if (firstLocation) {
firstLocation = false;
}
}
});
//startWorldUpdate();
// deactivate standard map
mMap.setMapType(GoogleMap.MAP_TYPE_NONE);
// add custom map tiles
addIngressTiles();
}
private void loadAssets()
{
int portalSize = 80;
mPortalIconResistance = Bitmap.createScaledBitmap(getBitmapFromAsset("portalTexture_RESISTANCE.png"), portalSize, portalSize, true);
mPortalIconEnlightened = Bitmap.createScaledBitmap(getBitmapFromAsset("portalTexture_ALIENS.png"), portalSize, portalSize, true);
mPortalIconNeutral = Bitmap.createScaledBitmap(getBitmapFromAsset("portalTexture_NEUTRAL.png"), portalSize, portalSize, true);
mXMParticleIcon = Bitmap.createScaledBitmap(getBitmapFromAsset("particle.png"), 10, 10, true);
}
private Bitmap getBitmapFromAsset(String name)
{
AssetManager assetManager = getActivity().getAssets();
InputStream istr;
Bitmap bitmap = null;
try {
istr = assetManager.open(name);
bitmap = BitmapFactory.decodeStream(istr);
} catch (IOException e) {
return null;
}
return bitmap;
}
private void addIngressTiles()
{
// create a custom tile provider with an ingress-like map
TileProvider tiles = new UrlTileProvider(256, 256) {
@Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
final String apistyle = "s.e%3Al%7Cp.v%3Aoff%2Cs.e%3Ag%7Cp.c%3A%23ff000000%2Cs.t%3A3%7Cs.e%3Ag%7Cp.c%3A%23ff5e9391";
final String style = "59,37%7Csmartmaps";
final String format = "http://mt1.googleapis.com/vt?lyrs=m&src=apiv3&hl=de-DE&x=%d&s=&y=%d&z=%d&s=Galileo";
String mapUrl = String.format(Locale.US, format, x, y, zoom);
mapUrl += "&apistyle=" + apistyle + "&style=" + style;
URL url = null;
try {
url = new URL(mapUrl);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
return url;
}
};
TileOverlayOptions tileOverlay = new TileOverlayOptions();
tileOverlay.tileProvider(tiles);
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(tiles));
}
private void startWorldUpdate()
{
// TODO: problems blocking execution and causing out-of-memory exception
final Handler uiHandler = new Handler();
long updateInterval = mGame.getKnobs().getScannerKnobs().getUpdateIntervalMS();
Timer updateTimer = new Timer();
updateTimer.scheduleAtFixedRate(new TimerTask() {
final Handler timerHandler = new Handler();
@Override
public void run()
{
uiHandler.post(new Runnable() {
@Override
public void run()
{
// get map boundaries (on ui thread)
LatLng northeast = mMap.getProjection().getVisibleRegion().latLngBounds.northeast;
LatLng southwest = mMap.getProjection().getVisibleRegion().latLngBounds.southwest;
final S2LatLngRect region = S2LatLngRect.fromPointPair(S2LatLng.fromDegrees(southwest.latitude, southwest.longitude),
S2LatLng.fromDegrees(northeast.latitude, northeast.longitude));
// update world (on timer thread)
timerHandler.post(new Runnable() {
@Override
public void run()
{
if (mGame.getLocation() != null)
updateWorld(region, uiHandler);
}
});
}
});
}
}, 0, updateInterval);
}
private synchronized void updateWorld(final S2LatLngRect region, final Handler uiHandler)
{
// handle interface result (on timer thread)
final Handler resultHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
// draw xm particles
drawXMParticles();
new Thread(new Runnable() {
@Override
public void run()
{
// draw game entities
Map<String, GameEntityBase> entities = mGame.getWorld().getGameEntities();
Set<String> keys = entities.keySet();
for (String key : keys) {
final GameEntityBase entity = entities.get(key);
uiHandler.post(new Runnable() {
@Override
public void run()
{
if (entity.getGameEntityType() == GameEntityBase.GameEntityType.Portal)
drawPortal((GameEntityPortal)entity);
else if (entity.getGameEntityType() == GameEntityBase.GameEntityType.Link)
drawLink((GameEntityLink)entity);
else if (entity.getGameEntityType() == GameEntityBase.GameEntityType.ControlField)
drawField((GameEntityControlField)entity);
}
});
}
Log.d("ScannerView", "world updated");
}
}).start();
return true;
}
});
// get objects (on new thread)
new Thread(new Runnable() {
@Override
public void run()
{
mGame.intGetObjectsInCells(region, resultHandler);
}
}).start();
}
private void drawXMParticles()
{
// draw xm particles
/*World world = mGame.getWorld();
Map<String, XMParticle> xmParticles = world.getXMParticles();
Set<String> keys = xmParticles.keySet();
for (String key : keys) {
XMParticle particle = xmParticles.get(key);
final Utils.LocationE6 location = particle.getCellLocation();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
BitmapDescriptor icon = BitmapDescriptorFactory.fromBitmap(mXMParticleIcon);
mMap.addMarker(new MarkerOptions()
.position(location.getLatLng())
.icon(icon));
}
});
}*/
}
private void drawPortal(final GameEntityPortal portal)
{
final Team team = portal.getPortalTeam();
if (mMap != null) {
// only update if marker has not yet been added
if (!mMarkers.containsKey(portal.getEntityGuid())) {
final com.norman0406.slimgress.API.Common.Location location = portal.getPortalLocation();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Bitmap portalIcon = null;
if (team.getTeamType() == Team.TeamType.Resistance)
portalIcon = mPortalIconResistance;
else if (team.getTeamType() == Team.TeamType.Enlightened)
portalIcon = mPortalIconEnlightened;
else
portalIcon = mPortalIconNeutral;
BitmapDescriptor icon = BitmapDescriptorFactory.fromBitmap(portalIcon);
MarkerOptions marker = new MarkerOptions();
marker.position(location.getLatLng())
.title(portal.getPortalTitle())
.icon(icon)
.anchor(0.5f, 0.5f);
Marker m = mMap.addMarker(marker);
mMarkers.put(portal.getEntityGuid(), m);
}
});
}
}
}
private void drawLink(final GameEntityLink link)
{
if (mMap != null) {
// only update if line has not yet been added
if (!mLines.containsKey(link.getEntityGuid())) {
final com.norman0406.slimgress.API.Common.Location origin = link.getLinkOriginLocation();
final com.norman0406.slimgress.API.Common.Location dest = link.getLinkDestinationLocation();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
int color = 0xff0000ff; // blue without alpha
Team team = link.getLinkControllingTeam();
if (team.getTeamType() == Team.TeamType.Enlightened)
color = 0xff00ff00; // green without alpha
PolylineOptions line = new PolylineOptions();
line.add(origin.getLatLng());
line.add(dest.getLatLng());
line.color(color);
line.width(2);
line.zIndex(2);
Polyline l = mMap.addPolyline(line);
mLines.put(link.getEntityGuid(), l);
}
});
}
}
}
private void drawField(final GameEntityControlField field)
{
if (mMap != null) {
// only update if line has not yet been added
if (!mPolygons.containsKey(field.getEntityGuid())) {
final com.norman0406.slimgress.API.Common.Location vA = field.getFieldVertexA().getPortalLocation();
final com.norman0406.slimgress.API.Common.Location vB = field.getFieldVertexB().getPortalLocation();
final com.norman0406.slimgress.API.Common.Location vC = field.getFieldVertexC().getPortalLocation();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
int color = 0x320000ff; // blue with alpha
Team team = field.getFieldControllingTeam();
if (team.getTeamType() == Team.TeamType.Enlightened)
color = 0x3200ff00; // green with alpha
PolygonOptions polygon = new PolygonOptions();
polygon.add(vA.getLatLng());
polygon.add(vB.getLatLng());
polygon.add(vC.getLatLng());
polygon.fillColor(color);
polygon.strokeWidth(0);
polygon.zIndex(1);
Polygon p = mMap.addPolygon(polygon);
mPolygons.put(field.getEntityGuid(), p);
}
});
}
}
}
}